package net.kleinhenz.norabase.server.datasource;

import com.google.appengine.api.datastore.KeyFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import net.kleinhenz.norabase.client.datasource.CommentDSService;
import net.kleinhenz.norabase.server.BeanHelper;
import net.kleinhenz.norabase.server.EMF;
import net.kleinhenz.norabase.server.model.Battlegroup;
import net.kleinhenz.norabase.shared.SortOrientation;
import net.kleinhenz.norabase.shared.TextMatchStyle;
import net.kleinhenz.norabase.shared.model.BattlegroupComment;
import net.kleinhenz.norabase.shared.model.Comment;
import net.kleinhenz.norabase.shared.model.FetchResult;
import net.kleinhenz.norabase.shared.model.RuneComment;

public class CommentDSServiceImpl extends RemoteServiceServlet implements CommentDSService {

	private static final long serialVersionUID = 1L;

    static int id;

	@Override
	public FetchResult<Comment> fetch(final int start, final int end,
			final Map<String, SortOrientation> sortSpec,
			final TextMatchStyle textMatchStyle, 
			final Map<String, Object> criteria, String battlegroupId, Integer runeId, String runeType) {

		List<Comment> result = new ArrayList<Comment>();
		EntityManager em = EMF.get().createEntityManager();
		
		if (battlegroupId!=null) {
			Battlegroup bg = em.find(Battlegroup.class, battlegroupId);
			for (net.kleinhenz.norabase.server.model.BattlegroupComment c : bg.getComments())
				result.add(copy(c));
		}
		else if (runeId!=null && runeType!=null) {
			Query q = em.createQuery("select from " + net.kleinhenz.norabase.server.model.RuneComment.class.getName() + " where runeId=:runeId and type=:type");
			q.setParameter("runeId", runeId);
			q.setParameter("type", runeType);

			for (Object c : q.getResultList()) {
				result.add(copy((net.kleinhenz.norabase.server.model.RuneComment)c));
			}
		} else
			return new FetchResult<Comment>();
			
		em.close();
		
		// filtering
		List<Comment> toRemove = new ArrayList<Comment>();
		for (Comment c : result) {
			try {
				for (Map.Entry<String, Object> ce : criteria.entrySet()) {
					Object cVal = BeanHelper.getPropertyValue(c, ce.getKey());
					Object ceVal = ce.getValue();
					
					if (cVal instanceof Integer && ceVal instanceof Integer && ((Integer)cVal).intValue()!=((Integer)ceVal).intValue())
						toRemove.add(c);
					// FIXME Booolean cVal XOR ceVal -> remove
					else if (cVal instanceof String && ceVal instanceof String) {
						if (TextMatchStyle.EXACT.equals(textMatchStyle) && cVal!=null && !(((String)cVal).equals((String)ceVal)))
							toRemove.add(c);
						else if (TextMatchStyle.SUBSTRING.equals(textMatchStyle) && cVal!=null && !(((String)cVal).contains((String)ceVal)))
							toRemove.add(c);
					}
				}
			} catch (Exception e) {
				toRemove.add(c);
			}
		}
		result.removeAll(toRemove);

		// sorting
		if (sortSpec!=null)
			Collections.sort(result, new Comparator<Comment>() {
				@Override
				public int compare(Comment o1, Comment o2) {

				    /* Should return:
				     * o1>o2 1
				     * o1<o2 -1
				     * o1=o2 0
				     */
					int order = 0;
					for (Map.Entry<String, SortOrientation> se : sortSpec.entrySet()) {

						try {
							Object value1 = BeanHelper.getPropertyValue(o1, se.getKey());
							Object value2 = BeanHelper.getPropertyValue(o2, se.getKey());
							if (value1 instanceof Integer && value2 instanceof Integer) {
								if ((Integer)value1<(Integer)value2)
									order=-1;
								else if ((Integer)value2>(Integer)value1)
									order=1;
							} else 	if (value1 instanceof Double && value2 instanceof Double) {
								if ((Double)value1<(Double)value2)
									order=-1;
								else if ((Double)value2>(Double)value1)
									order=1;
	
							} else if (value1 instanceof String && value2 instanceof String) {
								order=((String)value2).compareTo((String)value1);
							} else if (value1 instanceof Date && value2 instanceof Date) {
								if (((Date)value1).after((Date)value2))
									order=1;
								else if (((Date)value1).after(((Date)value2)))
									order=-1;
								else 
									order = 0;
							}	
							if (SortOrientation.DESCENDING.equals(se.getValue()))
								order*=-1;
						} catch (Exception e) {
							return 0;
						}
					}
					return order;
				}
			});

		// truncating - sublist not working, serialization problems
		FetchResult<Comment> finalResult = new FetchResult<Comment>();
		finalResult.fullSize = result.size();
		for (int n = start; n<(end>result.size()?result.size():end); n++)
				finalResult.add(result.get(n));
		return finalResult;
	}

	@Override
	public Comment add(Comment comment) {

		if (comment==null)
			return null;
		
		EntityManager em = EMF.get().createEntityManager();

		if (comment instanceof BattlegroupComment) {

			Battlegroup bg = em.find(Battlegroup.class, ((BattlegroupComment)comment).getBattlegroupId());
			net.kleinhenz.norabase.server.model.BattlegroupComment c = copy((BattlegroupComment)comment, bg);
			bg.getComments().add(c);
			em.merge(bg);
			em.persist(c);
			em.close();
			comment.setId(KeyFactory.keyToString(c.getId()));			
		
		} else if (comment instanceof RuneComment) {
		
			net.kleinhenz.norabase.server.model.RuneComment c = copy((RuneComment)comment);			
			em.persist(c);
			em.close();
			comment.setId(KeyFactory.keyToString(c.getId()));						
		}
	
		return comment;
	}

	@Override
	public Comment remove(Comment comment) {

		// FIXME implement: remove comment, remove reference from account and battlegroup
		return null;
	}

	private void copyBasics(Comment from ,net.kleinhenz.norabase.server.model.Comment to) {
		
		to.setId(from.getId()==null?null:KeyFactory.stringToKey(from.getId()));
		to.setAccountId(from.getAccountId());
		to.setComment(from.getComment());
		to.setDate(from.getDate());
		to.setRating(from.getRating());
	}

	private void copyBasics(net.kleinhenz.norabase.server.model.Comment from, Comment to) {
		
		to.setId(from.getId()==null?null:KeyFactory.keyToString(from.getId()));
		to.setAccountId(from.getAccountId());
		to.setComment(from.getComment());
		to.setDate(from.getDate());
		to.setRating(from.getRating());
	}
	
	private RuneComment copy(net.kleinhenz.norabase.server.model.RuneComment from) {
			
		RuneComment to = new RuneComment();
		copyBasics(from, to);
		to.setRuneId(from.getRuneId());
		to.setType(from.getType());
		
		return to;
	}

	private net.kleinhenz.norabase.server.model.RuneComment copy(RuneComment from) {
		
		net.kleinhenz.norabase.server.model.RuneComment to = new net.kleinhenz.norabase.server.model.RuneComment();
		copyBasics(from, to);
		to.setRuneId(from.getRuneId());
		to.setType(from.getType());
		
		return to;
	}

	private BattlegroupComment copy(net.kleinhenz.norabase.server.model.BattlegroupComment from) {

		BattlegroupComment to = new BattlegroupComment();
		copyBasics(from, to);
		to.setBattlegroupId(from.getBattlegroupId());
		
		return to;
	}
	
	private net.kleinhenz.norabase.server.model.BattlegroupComment copy(BattlegroupComment from, Battlegroup battlegroup) {

		net.kleinhenz.norabase.server.model.BattlegroupComment to = new net.kleinhenz.norabase.server.model.BattlegroupComment();
		copyBasics(from, to);
		to.setBattlegroupId(KeyFactory.keyToString(battlegroup.getId()));
		
		return to;
	}
}
